namespace Girvs.Infrastructure;

/// <summary>
/// Represents Nop engine
/// </summary>
public class GirvsEngine : IEngine
{
    /// <summary>
    /// Get I
    /// </summary>
    /// <returns>IServiceProvider</returns>
    protected IServiceProvider GetServiceProvider(IServiceScope scope = null)
    {
        if (scope == null)
        {
            var accessor = ServiceProvider?.GetService<IHttpContextAccessor>();
            var context = accessor?.HttpContext;
            return context?.RequestServices ?? AsyncLocalServiceProvider.Value ?? ServiceProvider;
        }

        return scope.ServiceProvider;
    }

    private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        //check for assembly already loaded
        var assembly = AppDomain
            .CurrentDomain.GetAssemblies()
            .FirstOrDefault(a => a.FullName == args.Name);
        if (assembly != null)
            return assembly;

        //get assembly from TypeFinder
        var tf = Resolve<ITypeFinder>();
        assembly = tf?.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
        return assembly;
    }

    /// <summary>
    /// Add and configure services
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    /// <param name="configuration">Configuration of the application</param>
    public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
    {
        //find startup configurations provided by other assemblies
        var typeFinder = new WebAppTypeFinder();
        //register engine
        services.AddSingleton<IEngine>(this);

        //register type finder
        services.AddSingleton<ITypeFinder>(typeFinder);

        ServiceProvider = services.BuildServiceProvider();

        var startupConfigurations = typeFinder.FindOfType<IAppModuleStartup>();

        //create and sort instances of startup configurations
        var instances = startupConfigurations
            .Select(startup => (IAppModuleStartup)Activator.CreateInstance(startup))
            .OrderBy(startup => startup?.Order);

        //configure services
        foreach (var instance in instances)
            instance?.ConfigureServices(services, configuration);

        //resolve assemblies here. otherwise, plugins can throw an exception when rendering views
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    }

    /// <summary>
    /// Configure HTTP request pipeline
    /// </summary>
    /// <param name="application">Builder for configuring an application's request pipeline</param>
    public void ConfigureRequestPipeline(IApplicationBuilder application, IWebHostEnvironment env)
    {
        ServiceProvider = application.ApplicationServices;

        //find startup configurations provided by other assemblies
        var typeFinder = Resolve<ITypeFinder>();
        var startupConfigurations = typeFinder.FindOfType<IAppModuleStartup>();

        //create and sort instances of startup configurations
        var instances = startupConfigurations
            .Select(startup => (IAppModuleStartup)Activator.CreateInstance(startup))
            .OrderBy(startup => startup?.Order);

        //configure request pipeline
        foreach (var instance in instances)
            instance?.Configure(application, env);
    }

    /// <summary>
    /// Configure pipeline endpoint
    /// </summary>
    /// <param name="endpointRouteBuilder"></param>
    public void ConfigureEndpointRouteBuilder(IEndpointRouteBuilder endpointRouteBuilder)
    {
        var typeFinder = Resolve<ITypeFinder>();
        var startupConfigurations = typeFinder.FindOfType<IAppModuleStartup>();

        var instances = startupConfigurations
            .Select(startup => (IAppModuleStartup)Activator.CreateInstance(startup))
            .OrderBy(startup => startup.Order);

        var logger = Resolve<ILogger<object>>();
        foreach (var instance in instances)
        {
            logger.LogInformation($"开始配置-{instance.GetType().Name}-相关的映射节点EndpointRouteBuilder");
            instance.ConfigureMapEndpointRoute(endpointRouteBuilder);
        }
    }

    /// <summary>
    /// Resolve dependency
    /// </summary>
    /// <param name="scope">Scope</param>
    /// <typeparam name="T">Type of resolved service</typeparam>
    /// <returns>Resolved service</returns>
    public T Resolve<T>(IServiceScope scope = null)
        where T : class
    {
        return (T)Resolve(typeof(T), scope);
    }

    /// <summary>
    /// Resolve dependency
    /// </summary>
    /// <param name="type">Type of resolved service</param>
    /// <param name="scope">Scope</param>
    /// <returns>Resolved service</returns>
    public object Resolve(Type type, IServiceScope scope = null)
    {
        return GetServiceProvider(scope)?.GetService(type);
    }

    /// <summary>
    /// Resolve dependencies
    /// </summary>
    /// <typeparam name="T">Type of resolved services</typeparam>
    /// <returns>Collection of resolved services</returns>
    public virtual IEnumerable<T> ResolveAll<T>()
    {
        return (IEnumerable<T>)GetServiceProvider().GetServices(typeof(T));
    }

    /// <summary>
    /// Resolve unregistered service
    /// </summary>
    /// <param name="type">Type of service</param>
    /// <returns>Resolved service</returns>
    public virtual object ResolveUnregistered(Type type)
    {
        Exception innerException = null;
        foreach (var constructor in type.GetConstructors())
        {
            try
            {
                //try to resolve constructor parameters
                var parameters = constructor
                    .GetParameters()
                    .Select(parameter =>
                    {
                        var service = Resolve(parameter.ParameterType);
                        if (service == null)
                            throw new GirvsException("Unknown dependency");
                        return service;
                    });

                //all is ok, so create instance
                return Activator.CreateInstance(type, parameters.ToArray());
            }
            catch (Exception ex)
            {
                innerException = ex;
            }
        }

        throw new GirvsException(
            "No constructor was found that had all the dependencies satisfied.",
            innerException
        );
    }

    /// <summary>
    /// Register dependencies
    /// </summary>
    /// <param name="services">Collection of service descriptors</param>
    /// <param name="appSettings">App settings</param>
    public virtual void RegisterDependencies(IServiceCollection services, AppSettings appSettings)
    {
        var typeFinder = new WebAppTypeFinder();

        //register engine
        services.AddSingleton<IEngine>(this);

        //register type finder
        services.AddSingleton<ITypeFinder>(typeFinder);

        //find dependency registrars provided by other assemblies
        var dependencyRegistrars = typeFinder.FindOfType<IDependencyRegistrar>();

        //create and sort instances of dependency registrars
        var instances = dependencyRegistrars
            .Select(dependencyRegistrar =>
                (IDependencyRegistrar)Activator.CreateInstance(dependencyRegistrar)
            )
            .OrderBy(dependencyRegistrar => dependencyRegistrar?.Order);

        //register all provided dependencies
        foreach (var dependencyRegistrar in instances)
            dependencyRegistrar?.Register(services, typeFinder, appSettings);

        services.AddSingleton(services);
    }

    public HttpContext HttpContext
    {
        get
        {
            var accessor = Resolve<IHttpContextAccessor>();
            return accessor?.HttpContext;
        }
    }

    // public Claim GetCurrentClaimByName(string name)
    // {
    //     if (HttpContext != null
    //         && HttpContext.User.Identity.IsAuthenticated)
    //     {
    //         return HttpContext.User.Claims.FirstOrDefault(x => x.Type == name);
    //     }
    //
    //     return new Claim(name, "");
    // }

    public IGirvsClaimManager ClaimManager
    {
        get
        {
            var claimManager = Resolve<IGirvsClaimManager>();
            claimManager?.SetFromHttpRequestToken();
            return claimManager;
        }
    }

    public TConfig GetAppModuleConfig<TConfig>()
        where TConfig : class, IAppModuleConfig
    {
        return Singleton<AppSettings>.Instance.Get<TConfig>();
    }

    public void SetCurrentThreadServiceProvider(IServiceProvider serviceProvider)
    {
        AsyncLocalServiceProvider.Value = serviceProvider;
    }

    public bool IsAuthenticated =>
        HttpContext?.User.Identity != null && HttpContext.User.Identity.IsAuthenticated;

    private static readonly AsyncLocal<IServiceProvider> AsyncLocalServiceProvider =
        new AsyncLocal<IServiceProvider>();

    public virtual IServiceProvider ServiceProvider { get; protected set; }
}
